package com.gmrz.webauthn.common;

import co.nstant.in.cbor.CborBuilder;
import co.nstant.in.cbor.CborEncoder;
import co.nstant.in.cbor.CborException;
import co.nstant.in.cbor.builder.MapBuilder;
import co.nstant.in.cbor.model.*;
import com.gmrz.webauthn.protocol.v1.processor.WebAuthnCborException;
import com.gmrz.webauthn.protocol.v1.processor.WebAuthnErrorCode;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.io.ByteArrayOutputStream;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;


public class CBOREncodeUtil {
    private static final Logger LOG = LogManager.getLogger(CBOREncodeUtil.class);

    public static byte[] cborMapEncode(Map<CBOREncodeValueWrapper, CBOREncodeValueWrapper> encodeData) throws WebAuthnCborException {
        List<DataItem> dataItems = cborMapConversion(encodeData);
        if (dataItems == null) {
            String errorMsg = " Unexpected size on the dataitems for CBOR encoding";
            LOG.error(errorMsg);
            throw new WebAuthnCborException(WebAuthnErrorCode.ERROR_CBOR_ENCODING_FAILED, errorMsg);
        }
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        try {
            new CborEncoder(baos).encode(dataItems);
        } catch (CborException e) {
            String errorMsg = "CBOR encode failed : ";
            LOG.error(errorMsg, e);
            throw new WebAuthnCborException(WebAuthnErrorCode.ERROR_CBOR_ENCODING_FAILED, e);
        }

        return baos.toByteArray();
    }

    private static List<DataItem> cborMapConversion(Map<CBOREncodeValueWrapper, CBOREncodeValueWrapper> encodeData)
            throws WebAuthnCborException {
        LOG.debug("Encode cbor map");
        MapBuilder<CborBuilder> builder = new CborBuilder().addMap();

        for (Entry<CBOREncodeValueWrapper, CBOREncodeValueWrapper> entry : encodeData.entrySet()) {
            CBOREncodeValueWrapper keyWrapper = entry.getKey();
            CBOREncodeValueWrapper valueWrapper = entry.getValue();
            builder.put(getEncodedValue(keyWrapper), getEncodedValue(valueWrapper));
        }

        return builder.end().build();
    }

    public static DataItem getEncodedValue(CBOREncodeValueWrapper ObjWrapper)
            throws WebAuthnCborException {
        LOG.debug("Encoding the following dataItem : {}", ObjWrapper.getObj().toString());
        Class<?> type = ObjWrapper.getType();
        Object dataObj = ObjWrapper.getObj();

        if (type == byte[].class) {
            return new ByteString((byte[]) dataObj);
        }
        if (type == String.class) {
            return new UnicodeString((String) dataObj);
        }
        if (type == BigInteger.class) {
            return new UnsignedInteger((BigInteger) dataObj);
        }
        if (type == Integer.class) {
            Integer obj = (Integer) dataObj;
            return intValuetoCborDataType(obj);
        }
        if (type == Long.class) {

            Long obj = (Long) dataObj;
            return intValuetoCborDataType(obj.intValue());
        }
        if (type == Double.class) {
            return new DoublePrecisionFloat(((Double) dataObj));
        }
        if (type == Map.class) {
            List<DataItem> resultList = cborMapConversion((Map) dataObj);
            return resultList.get(0);
        }
        if (type == ArrayList.class) {
            Array result = new Array();
            for (CBOREncodeValueWrapper item : (ArrayList<CBOREncodeValueWrapper>) dataObj) {
                result.add(getEncodedValue(item));
            }
            return result;
        }
        String errorMsg = "Unsupported object {} for cborEncoding";
        LOG.error(errorMsg, dataObj.toString());
        throw new WebAuthnCborException(WebAuthnErrorCode.ERROR_CBOR_ENCODING_FAILED, errorMsg);
    }

    private static DataItem intValuetoCborDataType(int value) {
        if (value >= 0) {
            return new UnsignedInteger(value);
        }
        return new NegativeInteger(value);
    }
}
